package com.atguigu.tingshu.common.minio.service.impl;

import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.minio.config.MinioProperties;
import com.atguigu.tingshu.common.minio.service.FileOpsService;
import com.atguigu.tingshu.common.util.MD5;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.StatObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.ErrorResponseException;
import org.springframework.web.multipart.MultipartFile;

/**
 * Description:
 *
 * @author Yanxinhao on 2025/1/14
 */
@Slf4j
public class FileOpsServiceImpl implements FileOpsService {

    @Autowired
    private MinioClient minioClient;
    @Autowired
    private MinioProperties properties;


    /**
     * 上传文件
     * @param file 用户传来的文件
     * @return 文件的访问路径 用于回显图片或文 件
     */
    @Override
    public String uploadFile(MultipartFile file) {
        try {
            /**
             * 1.防止同一个文件的重复上传，我们第一步要先做校验
             *  具体实现思路：
             *  根据传来的文件对象，将文件先转换为字节数组
             *  然后将字节数据转换为字符串对象，将字符串进行MD5加密后，将加密后的字符串作为文件名
             *  这样子一来，每当有一个新的文件要上传到minio中，我们都将通过这样的形式，先尝试去获取文件名
             *  然后将文件以我们加密得到后的文件名为准，加入到minio服务器中
             *  我们每次上传一个文件，都会根据得到的文件名进行校验，若文件名一致，则说明有同一个文件存在minio服务器中了
             *  我们就直接将哪个对应的文件的访问路径直接返回就ok了
             */
            //1.1 我们先获取文件的原始文件名，以得到文件的后缀字符串
            String originalFilename = file.getOriginalFilename();
            String fileNameSuffix = originalFilename.substring(originalFilename.lastIndexOf("."),originalFilename.length());
            //1.2 现在我们将对文件的字节数组进行加密操作，获取md5加密结果，并作为文件名前缀
            //获取文件的字节数组
            byte[] bytes = file.getBytes();
            //将文件的字节数组转换为字符串--->务必使用new String(),不可以使用String.valueOf()
            String fileNameStr = new String(bytes);
            //将字符串进行MD5加密,作为文件名前缀
            String fileNamePrefix = MD5.encrypt(fileNameStr);
            //1.3 根据前缀和后缀，组成最终的文件名
            String fileName = fileNamePrefix + fileNameSuffix;
            //2.现在我们根据加密，获取了最终的文件名，接下来就是针对文件名进行去重判断
            try {
                //2.1 使用minio的客户端对象对对应桶中的重复文件名进行校验
                //此处，执行statObject对对应桶名的文件名进行校验
                //若执行当前语句，抛出了异常，那么就说明当前桶内，不存在该重复文件名的文件
                //那么我们就catch异常，并执行文件存入minio的逻辑
                //若当前桶内存在重复文件，那么就会正常执行，不会抛出异常，那么我们就将当前重复的文件返回即可
                minioClient.statObject(StatObjectArgs.builder()
                        .object(fileName)
                        .bucket(properties.getBucketName())
                        .build());
                log.info("桶内存在重复文件，不选择保存文件，将重复文件返回...");
                //2.2 直接将重复文件返回即可，无需再次重复存入
                return properties.getEndpointUrl() + "/" + properties.getBucketName() + "/" + fileName;
            } catch (Exception e) {
                //2.3 这里如果捕获到了异常，说明当前桶内不存在该重复文件名的文件，那么我们就执行存入逻辑
                //进行文件的上传操作
                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(properties.getBucketName())
                        .object(fileName)
//                        .contentType("image/png")
                        .stream(file.getInputStream(),file.getSize(),-1)
                        .build());
                // 2.4 组装上传内容的访问路径，并返回，主要用于前端回显
                return properties.getEndpointUrl() + "/" + properties.getBucketName() + "/" + fileName;
            }
        } catch (Exception e) {
            log.error("文件上传失败:{}",e.getMessage());
            throw new GuiguException(201, "文件上传失败");
        }
    }
}